<?php
class CMysql extends CDBBase implements IDatabase
{
    /**
     * @var PDO 对象
     */
    private $_pdo = null;

    private $_useTransaction = false;
    
    private $_isRollback = false;
    
    private $_isCommitFinish = false;
    
	/**
	 * @param bool $isProc是否存储过程
	 */
	function __construct ($dbConfigKey)
	{
		parent::__construct($dbConfigKey);
		
        if (!class_exists('PDO'))
        {
            $this->errorMsg("不支持PDO");
        }
        if(!isset($this->_dbConfig['dbPort']))
        {
        	$this->_dbConfig['dbPort'] = '3306';
        }
        if(!isset($this->connConfig['dbCharset']))
        {
        	$this->_dbConfig['dbCharset'] = 'utf8';
        }

        $this->connect();
	}

    /**
     * 初始化数据库连接
     * @param array $dbConfig 数据库配置信息
     * @return void
     */
    public function connect()
    {
        if ($this->_pdo == null)
        {
            try
           {
                $dsn = "mysql:host={$this->_dbConfig['dbHost']};port={$this->_dbConfig['dbPort']};dbname={$this->_dbConfig['dbName']}";

                $this->_pdo = new PDO($dsn, $this->_dbConfig['dbUser'], $this->_dbConfig['dbPwd'],array(PDO::ATTR_PERSISTENT => false));

                $this->_pdo->setAttribute(PDO::ATTR_TIMEOUT,1);
            }
            catch (Exception $e)
            {
                $this->errorMsg('数据库连接失败: ' . $e->getMessage());
            }
   			//if($this->getVersion() < 5)
   			//{
   			//	$this->closeConnect();
   			//	$this->errorMsg('数据库版本必须大于５.０'); 
   			//} 
            $this->_pdo->exec("SET NAMES {$this->_dbConfig['dbCharset']}");//设置连接字符集
        }
    }
    
    /**
     * 取mysql版本
     * @return string 
     */
    private function getVersion()
    {
        $mysqlVersion = $this->_pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
        return doubleval($mysqlVersion);
    }
	
    /**
     * 关闭数据库对象
     * @return void
     */
    public function closeConnect()
    {
        if ($this->_pdo != null)
        {
            $this->_pdo = null;
        }
    }
    
    public function hasBeginTransaction()
    {
    	return $this->_useTransaction;
    }
    
	public function hasRollBack()
    {
    	return $this->_isRollback;
    }
    
    public function beginTransaction()
    {
    	$this->_isCommitFinish = false;
    	$this->_useTransaction = true;
    	
    	$flag=$this->_pdo->beginTransaction();

    	if(!$flag)
    	{
    		$this->errorMsg("开始一个事务失败!");
    	}
    }
    
    public function commit()
    {
    	if($this->_useTransaction && !$this->_isRollback)
    	{
    		$flag = $this->_pdo->commit();
    		
    		$this->_isCommitFinish = $flag;
    	}
    	$this->_useTransaction = false;
    }
    
    public function rollBack()
    {
    	if($this->_useTransaction && !$this->_isRollback)
    	{
    		$this->_isRollback = true;
    		$this->_pdo->rollBack();
    	}
    }
    
    public function commitSuccess()
    {
    	return $this->_isCommitFinish;
    }
    
    /**
     * 返回当前链接的PDO对象
     * @return void
     */
    /*public function getPdo()
    {
    	return $this->_pdo;
    }*/
    
    /**
     * 返回一个数值表示此SqlCommand命令执行后影响的行数.注意不支持out类型参数的PROC
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return int 返回影响的行数.
     */
    public function executeNonQuery($cmdText, array $commandParameters=null)
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters);
    	
    	if($com)
    	{
	    	return $com->rowCount();
    	}
    	return 0;
    }
    
	public function executeReturnLastID($cmdText, array $commandParameters=null)
    {
    	$this->executeNonQuery($cmdText,$commandParameters);
    	
    	$cmdText = "select LAST_INSERT_ID();";
    	
    	return $this->executeScalar($cmdText,$commandParameters);
    }
    
    /**
     * 执行一个操作,并返回首行首列的内容.
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return mixed 并返回首行首列的内容.
     */
    public function executeScalar($cmdText, array $commandParameters=null)
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters);
    	if($com)
    	{
			return $com->fetchColumn(0);
    	}
    	
	    return false;
    }
    
	public function executeFetch($cmdText, array $commandParameters=null)
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters);
    	if($com)
    	{
    		return $com->fetch(PDO::FETCH_ASSOC);
    	}
    	return false;
    }

    /**
     * 返回一个数组集,如果有多个select也可以返回.可以通过字段下标和索引下标搜索
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return array;
     */
    public function executeDataSet($cmdText, array $commandParameters=null)
    {
    	return $this->fetchAll($cmdText,PDO::FETCH_ASSOC, $commandParameters);    	
    }

    /**
     * 返回一个数组集,如果有多个select也可以返回.只能通过索引下标搜索
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return array;
     */
    public function  executeDataSetIndex($cmdText, array $commandParameters=null)
    {
    	return $this->fetchAll($cmdText,PDO::FETCH_NUM, $commandParameters);    	
    }
      
    /**
     * 返回一个数组集,如果有多个select也可以返回.只能通过字段下标搜索
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数.
     * @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return array;
     */
    public function executeDataSetBOTH($cmdText, array $commandParameters=null)
    {
    	return $this->fetchAll($cmdText,PDO::FETCH_BOTH, $commandParameters);    	
    }
    
            
    /**
     * 返回一个PDOStatement对象,可以执行PDOStatement的对象;
     * @param bool $isProc 是否存储过程.
     * @param string $cmdText 执行的命令字符串,是存储过程时不带参数,如果不是存储过程时sql语句里的变量用'?'或者':paramName'.
     * @param  
     * array(array( mixed $parameter, mixed &$variables[, int $data_type[, int $length[, mixed $driver_options ]]])) $commandParameters,
     * 同pdo->bindParam()里的参数. @example array(array(1|':parameterName',$colour, PDO::PARAM_STR, 12));
     * @return PDOStatement;
     */
    private function getPDOStatement($cmdText, array $commandParameters=null)
    {
    	try{
    		
    		if($this->_useTransaction==true && $this->_isRollback == true)
    		{
    			return 0;
    		}
			//if(!is_string($cmdText)) $this->errorMsg('$cmdText 参数必须为string类型.');
			if(!is_array($commandParameters) && isset($commandParameters)) 
			{
				$this->errorMsg('$commandParameters 参数必须为array类型.');
			}
			
	    	$cmdText = $this->_isProc ? $this->getProcText($cmdText,$commandParameters) : $cmdText;

		    $com = $this->_pdo->prepare($cmdText);

		    $this->bindValue($com,$commandParameters);
		    $com->execute() or $this->errorMsgAndThrow($com->errorInfo());
		    return $com;
    	}
    	catch(Exception $ex)
    	{
    		$this->rollBack();
    		
    		$this->errorMsg($ex->getMessage());
    		//echo $ex->getMessage();
    		
    		/*if($this->_useTransaction)
    		{
    			$this->_isRollback = true;
    			$this->_pdo->rollBack();
    		}*/
    		//throw new Exception($ex);
    	}
    }

    private function fetchAll($cmdText, $fetchMode = PDO::FETCH_BOTH, array $commandParameters=null)
    {
    	$com = $this->getPDOStatement($cmdText, $commandParameters);
    	
    	if(!$com)
    	{
    		return false;
    	}
    	$com -> setFetchMode($fetchMode);
    	
    	$resArr = array();
    	do {
		    $rowset = $com->fetchAll();
		    if (!$rowset)$rowset = array();
		    
		    if($this->_isProc)
		    {
		    	if(!$rowset)
		    	{
		    		continue;
		    	}
		    }
		    
		    array_push($resArr,$rowset);
		}
		while ($com->nextRowset());

    	return count($resArr)==1?$resArr[0]:$resArr;
    }
    
    private function getProcText($cmdText, array $commandParameters=null)
    {
    	if(strpos($cmdText,'(')!== false) return $cmdText;
    	$params = '';
    	if(isset($commandParameters)){
	    	foreach($commandParameters as $k => $v)
	    	{
	   			$params .= ((count($v)==2)?'?':$v[0]).',';
	    	}
    	}
    	
    	if(strlen($params)>0)
    	{
    		$params = substr($params,0,strlen($params)-1);
    	}
    	return "call {$cmdText}({$params})";
    }
    
    private function bindValue(PDOStatement $com, array $commandParameters=null)
    {    
    	if(!isset($commandParameters))
    	{
    		return;
    	}
    	
    	$i=1;
    	$parameter = "";
    	$value = "";
    	$dataType = "";

	    foreach ($commandParameters as $k=>$v)
	    {
	    	//效验参数类型    	
			//bindParam
	    	//if(isset($v[3]))
	    	//	$com ->bindValue($v[0],$v[1],$v[2],$v[3]);
	    	//else 		
	    	// mixed $parameter , mixed $value [, int $data_type ]
	    	if(count($v)==2)
	    	{
	    		$parameter = $i;
	    		$value = $v[0];
	    		$dataType = $v[1]; 
	    	}
	    	else if(count($v)==3)
	    	{
		    	$parameter = $v[0];
		    	$value = $v[1];
		    	$dataType = $v[2]; 
	    	}
	    	else
	    	{
	    		$this->errorMsg("参数错误");
	    	}
	    	
	    	$i++;

	    	$flag = $com->bindValue($parameter,$value,$dataType);
	    	//var_dump($flag);
	    }    	
    }

    
	function __destruct ()
	{		
        $this->closeConnect();
	}
}

?>